/**
 * @fileoverview
 * 
 * Base implementation of buffer (data packet) related classes
 * 
 * @author Sebastian Brzuzek sebastian@brzuzek.pl
 */

goog.provide('uxperf.Buffer');
goog.provide('uxperf.BufferItem');
goog.provide('uxperf.BufferEvent');
goog.provide('uxperf.BufferItemEvent');

goog.require('uxperf.Storage');
goog.require('goog.array');
goog.require('goog.json');
goog.require('goog.Disposable');
goog.require('goog.events.Event');

/**
 * class for buffer of items related to monitored events
 *
 * @constructor
 * @extends {goog.Disposable}
 */
uxperf.Buffer = function () {
	goog.base(this);
	//dummy constructor
};
goog.inherits(uxperf.Buffer, goog.Disposable);

/**
 * reusable json serializer instance
 * @type {goog.json.Serializer}
 */
uxperf.Buffer.JSON_SERIALIZER = new goog.json.Serializer();

/**
 * buffer identifier
 * @type {string|null}
 */
uxperf.Buffer.prototype.id_ = null;

/**
 * set buffer id
 * @param {string} id
 * for internal use in queue
 */
uxperf.Buffer.prototype.setId = function (id) {
	if (goog.DEBUG) {
		if (id.indexOf('"') >= 0) throw Error('Buffer id can not contain " (quotation mark) character');
	}
	this.id_ = id;
};

/**
 * return buffer id
 * (in DEBUG mode throws error if id not set)
 * 
 * @return {string|null}
 * @throws {Error} if id not set (only in DEBUG mode)
 */
uxperf.Buffer.prototype.getId = function () {
	if (goog.DEBUG) {
		if (this.id_ === null) throw Error('Buffer id not set');
	}
	return this.id_;
};

/**
 * buffer items list
 * @type {Array.<uxperf.BufferItem|string>}
 */
uxperf.Buffer.prototype.items_ = [];

/**
 * return buffer items list
 * @return {Array.<uxperf.BufferItem|string>}
 */
uxperf.Buffer.prototype.getItems = function() {
  return this.items_;
};

/**
 * flag if there are mixed items or only strings
 * @type {boolean}
 */
uxperf.Buffer.prototype.mixed_ = false;

/**
 * Push new item into buffer
 * 
 * @param {uxperf.BufferItem|string} item
 * @public
 */
uxperf.Buffer.prototype.push = function (item) {
  if (!goog.isString(item)) this.mixed_ = true;
	this.items_.push(item);
};

/**
 * serialize buffer to json object
 * @return {Object} json
 */
uxperf.Buffer.prototype.serialize = function () {
  /** @type {Object} */
	var res = {
		'id' : this.id_,
    'storageid' : uxperf.Storage.getStorageId(),
		'items' : []
	};
  if (this.mixed_) {
    goog.array.forEach(this.items_, function(el) {
  		res['items'].push(goog.isObject(el) ? el.serialize() : el);
  	});
  } else {
    res['items'] = this.items_;
  }
	return res;
};

/**
 * serialize buffer to string
 * @return {string}
 */
uxperf.Buffer.prototype.toString = function() {
	return uxperf.Buffer.JSON_SERIALIZER.serialize(this.serialize());
};

/**
 * 
 * @param {string} type item type name
 * @constructor
 * @extends {goog.Disposable}
 */
uxperf.BufferItem = function (type) {
	goog.base(this);
	
	/**
	 * Type of item - same as type of sensor which created item
	 * @type {string}
	 */
	this.type_ = type;
	
	/**
	 * Time when item was created
	 * @type {number}
	 */
	this.timestamp_ = goog.now();
	
	/**
	 * Unique item identifier (maintained by queue)
	 * @type {number|null}
	 */
	this.id_ = null; //will be set later by queue
  
  /**
   * Custom data (should be easy to serialize with standards for json)
   * @type {*}
   */
  this.custom_ = undefined;
};
goog.inherits(uxperf.BufferItem, goog.Disposable);

/**
 * serialize buffer item to json object
 * used internally in toString and in Buffer serialization
 *
 * @return {Object}
 */
uxperf.BufferItem.prototype.serialize = function () {
	var res = {};
	res['id'] = this.getId();
	res['type'] = this.type_;
	res['timestamp'] = this.timestamp_;
  if (this.custom_) res['custom'] = this.custom_;
	
	return res;
};

/**
 * serialize buffer item to string compatible with json format
 * @return {string}
 */
uxperf.BufferItem.prototype.toString = function () {
	return uxperf.Buffer.JSON_SERIALIZER.serialize(this.serialize());
};

/**
 * retrieve item id from string representation of Buffer Item
 *
 * @param {string} json
 * @return {number} id
 * @throws {Error} if json is incorrect
 */
uxperf.BufferItem.idFromString = function (json) {
  if (!json || (json.length<7)) throw Error('JSON representation of BufferItem too short');

  //try use faster method with id at the beginning of string
  if ('{"id":' === json.substr(0,6)) {
    if (goog.DEBUG) LOG.info('BufferItem - idFromString - json: ' + json); 
    var id = json.substr(6,100);
    //id is number so we look for comma to determine length
    var comma = id.indexOf(',');
    if (comma>0) {
      id = id.substring(0,comma);
      if (goog.DEBUG) LOG.info('BufferItem - idFromString - id: ' + id);
      id = Number(id);
      if (!isNaN(id)) return id;
    }
  }
  var item = goog.json.parse(json);
  return item['id'];
};

/**
 * set new id of item
 * for internal use in queue
 * @param {number} id
 */
uxperf.BufferItem.prototype.setId = function(id) { this.id_ = id; };

/**
 * return item id
 * (in DEBUG mode throws error if id not set)
 * 
 * @return {number|null}
 * @throws {Error} if id not set (only in DEBUG mode)
 */
uxperf.BufferItem.prototype.getId = function() {
	if (goog.DEBUG) {
		if (this.id_ === null) throw Error('Buffer item id not set');
	}
	return this.id_;
};

/**
 * update custom data to send with item
 * 
 * @param {*} data if current custom data is object and new one is object
 *            than it will be merge them with overriding values with new if available
 *            if current custom data is array and new one is array it will append new one
 *            other values will simply replace old one
 *            NB! there is no formal restriction
 *            on type of data, however you should
 *            be sure to use something safe to serialize
 *            with standard json serializer of Closure Library
 */
uxperf.BufferItem.prototype.updateCustom = function (data) {
  if (!data) return;
  if (goog.isFunction(data)) {
    data = data.call(this);
  }
  if (this.custom_) {
    if (goog.isObject(this.custom_) && goog.isObject(data)) {
      this.custom_ = uxperf.utils.overrides(this.custom_, data);
      return;
    }
    if (goog.isArray(this.custom_) && goog.isArray(data)) {
      goog.array.extend(/** @type {Array} */ (this.custom_), /** @type {Array} */ (data));
      return;
    }
  }
  this.custom_ = data;
};

/**
 * Event with buffer
 * 
 * @param {string} type Event Type
 * @param {uxperf.Buffer} buffer
 * @param {Object=} opt_target Reference to the object that is the target of
 *     this event. It has to implement the {@code EventTarget} interface
 *     declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
 * @constructor
 * @extends {goog.events.Event}
 */
uxperf.BufferEvent = function(type, buffer, opt_target) {
	goog.base(this, type, opt_target);
	this.buffer = buffer;
};
goog.inherits(uxperf.BufferEvent, goog.events.Event);

/** @override */
uxperf.BufferEvent.prototype.disposeInternal = function() {
	uxperf.BufferEvent.superClass_.disposeInternal.call(this);
	delete this.buffer;
};

/**
 * Event with buffer item
 * 
 * @param {string} type Event Type
 * @param {uxperf.BufferItem} item buffer item
 * @param {Object=} opt_target Reference to the object that is the target of
 *     this event. It has to implement the {@code EventTarget} interface
 *     declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
 * @constructor
 * @extends {goog.events.Event}
 */
uxperf.BufferItemEvent = function(type, item, opt_target) {
	goog.base(this, type, opt_target);
	this.item = item;
};
goog.inherits(uxperf.BufferItemEvent, goog.events.Event);

/** @override */
uxperf.BufferItemEvent.prototype.disposeInternal = function() {
	uxperf.BufferItemEvent.superClass_.disposeInternal.call(this);
	delete this.item;
};
